16. Advanced Synchronization
Advanced Synchronization
In this section, you'll learn about some synchronization tools that may come in handy when the synchronized
keyword does not fit your use case.
ND079 JPND C2 L05 A18 Advanced Synchronization V2
When to Use the ReentrantLock
Class
The synchronized
keyword only works with blocks of code — either explicit curly braces after the lock object, or an entire method.
Sometimes, you may want to take ownership of the lock in one method, and release ownership of the lock in another method. Doing this is impossible using only the synchronized
keyword.
For these kinds of situations, Java provides the ReentrantLock
class.
Here's a code example using ReentrantLock
:
public final class VotingApp {
private final Map<String, Integer> votes = new HashMap<>();|
private final Lock lock = new ReentrantLock();
public void castVote(String performer) {
lock.lock();
try {
votes.compute(
performer, (k, v) -> (v == null) ? 1 : v + 1);
} finally {
lock.unlock();
}
}
}
The key feature here is that the lock()
and unlock()
methods can be called anywhere, by any thread that has a reference to the lock
object.
You could for example, have a data structure like a HashMap
that stores ReentrantLock
s as values. Maybe the keys are file names, or some other resource that is shared between threads.
What is a Deadlock?
A deadlock is a kind of concurrent programming error that happens when all threads are stuck waiting for some action. But that action never happens, meaning no threads are ever able to make progress.
One common source of deadlocks is when a thread takes ownership of a lock, but never releases it. This can happen when two threads need the same locks, but take ownership of them in a different order.
SOLUTION:
- If you only need block-scoped locking, `synchronized` is a lot simpler.
Other Kinds of Locks
ReentrantLock
is just one kind of lock object.
If you have a moment, take a look in the java.util.concurrent.locks
package, which contains other kinds of specialized locks.
It contains other kinds of specialty locks, such as ReadWriteLock
. This can be useful when you have some threads that only need to read a shared resource, but other threads need to modify the resource.
Semamphores
The synchronized
keyword only allows one thread at a time within a guarded block of code. What do you do if you need to allow more than one thread?
The Semaphore
class can help with this scenario.
To create a Semaphore
you tell it how many permits it can give out:
Semaphore semaphore = new Semaphore(4);
Usually the number passed to the Semaphore
constructor is the number of threads you want to allow in the guarded code, but you could also have a thread taking multiple permits if you want.
Here's how threads obtain and release permits from the semaphore:
try {
semaphore.acquire();
// Up to 4 threads can be executing here in parallel!
// ...
} finally {
// Give another thread a turn.
semaphore.release();
}
You might have noticed that if you create a Semaphore
with only 1 permit to give out, it behaves the same as a ReentrantLock
.
When a lock only allows one thread, you will sometimes here it called a mutex, which is a portmanteau of the term "mutual exclusion".